<!--TODO  自定义ModalForm 初始版本，有很多可优化项， 目前支持数据代理，联动重新拉取代理数据已支持 ，已经满足大多数使用场景功能
          tips： 更多规则的重载代理数据，还需自行插入valueSlot模板，订阅者自行实现
 -->
<script lang="tsx" name="BaseModelForm">
  import { ref, reactive, computed, watch, nextTick, defineComponent, onMounted, unref } from 'vue';
  import { error, log, confirm, message, deepMerge } from './utlis';
  import { ElDialog, ElForm, ElRow, ElCol, ElFormItem } from 'element-plus';
  import type { FormRules, FormInstance } from 'element-plus';
  import { Component, menuType, FormFieldItem, ModelData } from './../types';
  import {
    omit,
    cloneDeep,
    isArray,
    isFunction,
    isObject,
    isEqual,
    set,
    isString,
    upperFirst,
    isEmpty,
  } from 'lodash-es';
  import { componentMap } from './../componentMap';
  import { BaseModelFormActionType } from './hooks/useBaseModelForm';
  import { basicProps } from './props';
  import type { BasicPropsActionType } from './props';

  export default defineComponent({
    name: 'BaseModelForm',
    components: {
      ElDialog,
      ElForm,
      ElRow,
      ElCol,
      ElFormItem,
    },
    inheritAttrs: true,
    props: basicProps,
    // 订阅者如需响应式表单数据需要绑定 v-model:form = formData
    emits: [
      'register',
      'update:show',
      'update:type',
      'update:form',
      'update:loading',
      'update:title',
      'update:disabled',
      'submit',
      'close',
    ],
    setup(_props, context) {
      const { slots, emit, expose, attrs } = context;
      // TODO  适配useModelForm 合并形参props 两种使用方式 hooks 形参 or props 传递属性
      const props: Partial<BasicPropsActionType> = reactive({});
      const formRef = ref<FormInstance>();
      const data = reactive<ModelData>({
        type: '',
        title: '',
        show: false,
        disabled: false,
        loading: false,
      });

      // ! 适配原始v-bind属性传值写法 兼容两种方式性能就会很差些
      const formData = computed<any>({
        set: (obj) => {
          if (isEqual(props.form, obj)) {
            return;
          } else {
            emit('update:form', obj);
            props.form = obj;
          }
        },
        get: () => {
          return !isEmpty(_props.form) ? _props.form : props.form;
        },
      });

      const loading = computed<boolean>({
        set: (bol) => {
          emit('update:loading', bol);
          data.loading = bol;
        },
        get: () => {
          return !!props.loading || !!data.loading;
        },
      });
      const title = computed<string | undefined>({
        set: (str) => {
          emit('update:title', str);
          data.title = str;
        },
        get: () => {
          return props.title || data.title;
        },
      });

      const show = computed<boolean>({
        set: (bol) => {
          emit('update:show', bol);
          data.show = bol;
        },
        get: () => {
          return !!props.type || !!data.show;
        },
      });

      const type = computed<menuType>({
        set: (str) => {
          emit('update:type', str);
          data.type = str;
        },
        get: () => {
          return props.type || data.type;
        },
      });

      const disabled = computed<boolean>({
        set: (bol) => {
          emit('update:disabled', bol);
          data.disabled = bol;
        },
        get: () => {
          return !!props.disabled || !!data.disabled;
        },
      });

      const formFields = computed<FormFieldItem<any>[]>(() => {
        if (!isArray(props?.formFields)) return [];
        return props?.formFields.filter((e) => {
          if (e && e.children && e.children.length) {
            e.children = e.children.filter((e) => e);
            return e;
          } else {
            return e && e;
          }
        });
      });

      const rules = computed<FormRules<any>>(() => {
        const rulesObj: FormRules = {};
        if (!isArray(props?.formFields)) return rulesObj;
        const list = props?.formFields.filter((e) => {
          if (e && e.children && e.children.length) {
            e.children = e.children.filter((e) => e);
            return e;
          } else {
            return e && e;
          }
        });
        function treeRules(fields: FormFieldItem<any>[]) {
          fields.forEach(({ prop, form, children }) => {
            if (isArray(children) && children?.length) {
              treeRules(children);
            } else if (isArray(form?.formItem?.rules) && form?.formItem?.rules?.length) {
              rulesObj[prop as string] = form?.formItem?.rules;
            }
          });
        }
        treeRules(list);
        return rulesObj;
      });
      /**
       * 安全地设置组件props，避免只读属性赋值错误
       * @description 在Vue 3中，某些情况下props对象可能被冻结，直接赋值会导致运行时错误
       * @param {Partial<BasicPropsActionType>} propsRef - 要设置的props对象
       */
      const setProps = async (propsRef: Partial<BasicPropsActionType>) => {
        const _props = cloneDeep(deepMerge(props || {}, propsRef));

        // 安全地设置props，避免直接修改可能的只读属性
        try {
          Object.keys(_props).map((key) => {
            if (key !== 'form') {
              // 检查属性是否可写
              const descriptor = Object.getOwnPropertyDescriptor(props, key);
              if (!descriptor || descriptor.writable !== false) {
                set(props, key, _props[key]);
              }
            }
          });

          // TODO form 数据源不合并 覆盖
          if (isObject(_props.form)) {
            set(props, 'form', _props.form);
            formData.value = _props.form;
          }
        } catch (error) {
          console.warn('设置props时遇到只读属性:', error);
          // 回退到创建新对象的方式
          const newProps = { ...props, ..._props };
          Object.assign(props, newProps);
        }
        console.log(props);
      };

      watch(
        () => _props,
        () => {
          setProps(_props);
        },
        {
          immediate: true,
          deep: true,
        },
      );

      const getFormRef = () => unref<FormInstance | undefined>(formRef);

      // 表单重置
      const resetForm = async () => {
        getFormRef()?.resetFields();
        await nextTick();
        setTimeout(() => {
          getFormRef()?.validate();
        }, 20);
      };

      function open(_type: menuType, _title?: string) {
        //TODO 弹出之后拷贝原有数据进行修改提示
        open.prototype.isChangeFormData = false;
        type.value = _type;
        disabled.value = false;
        title.value = _title || '查看';
        show.value = true;
        if (_type === 'cat') {
          disabled.value = true;
        } else {
          title.value = _title || (_type === 'add' ? '新增' : '编辑');
        }
        nextTick(() => {
          setTimeout(() => {
            getFormRef()?.clearValidate();
          }, 400);
        });
      }

      async function close(flag = true, showCloseMessage = false) {
        // TODO 如果做了修改，则提示用户
        let message = 'confirm';
        if (
          open.prototype.isChangeFormData &&
          !['cat', 'share'].includes(type.value) &&
          showCloseMessage
        ) {
          // 确认窗体
          message = await confirm(props.closeMessage as string);
        }
        loading.value = false;
        if (flag && message === 'confirm') {
          await resetForm();
          show.value = false;
          emit('close');
        }
      }

      async function submit() {
        const _formData = cloneDeep(formData.value);
        const _formDataKeys = [
          ...(unref(formFields).map((e) => e.prop) as string[]),
          ...Object.keys(_formData),
        ];
        function treeKeys(fields: FormFieldItem<any>[], keys: string[]) {
          fields.forEach(({ prop, children }) => {
            isString(prop) && keys.push(prop);
            if (isArray(children) && children?.length) {
              treeKeys(children, keys);
            }
          });
        }
        treeKeys(unref(formFields), _formDataKeys);
        _formDataKeys.map((key) => {
          if ([undefined, '', null].includes(_formData[key])) {
            _formData[key] = null;
          }
        });
        log(`send formData: ${JSON.stringify(_formData, null, 4)}`);
        let valid = true;
        if (props.isValidate) {
          valid = (await formRef.value?.validate().catch((error) => {
            const key = Object.keys(error)[0];
            const messageText =
              error[key]?.message ?? error[key]?.[0]?.message ?? '请检查表单必填项~';
            if (props.ruleMessageType === 'alert') {
              alert(messageText);
            } else if (props.ruleMessageType === 'message') {
              message(messageText);
            }
          })) as boolean;
        }
        if (valid) {
          loading.value = true;
          try {
            const _formData = cloneDeep(formData.value);
            Object.keys(_formData).map((key) => {
              if (_formData[key] === undefined) {
                _formData[key] = null;
              }
            });
            emit(
              'submit',
              {
                formRef: formRef.value,
                formData: _formData,
                type: data.type,
              },
              close,
            );
          } catch (e) {
            close(false);
            error(String(e));
          }
        }
      }

      const updateFormData = (form: any) => {
        formData.value = cloneDeep(form);
      };

      const baseModelFormActionType: Partial<BaseModelFormActionType> = {
        open,
        close,
        getFormRef,
        clearValidate: async () => getFormRef()?.clearValidate(),
        getLoading: () => unref(loading),
        getFormData: () => unref(formData),
        getDisabled: () => unref(disabled),
        getBindValue: () => ({ ...attrs, ...props }),
        getProps: (...paths) => ({ ..._props }),
        setProps,
        updateFormData,
        resetForm,
        submit,
      };

      expose(baseModelFormActionType);
      onMounted(() => {
        emit('register', baseModelFormActionType);
      });

      return () => {
        /**
         * 安全地获取内容slots，避免传递只读属性
         * @description 通过浅拷贝避免直接传递可能的只读formData和field对象
         * @param {FormFieldItem<any>} field - 表单字段配置
         * @returns {Object|null} slots对象或null
         */
        const getContentSlots = (field: FormFieldItem<any>) => {
          const slots = field.form?.slots;
          if (isObject(slots)) {
            const _slots: { [key: string]: any } = {};
            Object.keys(slots).map((prop: string) => {
              if (isFunction(slots[prop])) {
                _slots[prop] = (...args: any[]) => {
                  // TODO 插入插槽内容的后代形参没有重写
                  // 安全地创建参数对象，避免直接传递可能的只读属性
                  const safeParams = {
                    params: args,
                    field: { ...field }, // 浅拷贝避免只读问题
                    formData: { ...formData.value }, // 浅拷贝避免响应式只读问题
                    type: data.type,
                  };
                  return slots[prop](safeParams, ...args);
                };
              }
            });
            return _slots;
          } else {
            return null;
          }
        };

        const getContent = (field: FormFieldItem<any>) => {
          const { form, valueSlot, prop, label } = field;
          const slot = slots[prop as string] ?? valueSlot;

          if (isFunction(slot)) {
            return slot({
              field: field,
              formData: formData.value,
              type: data.type,
            });
          }
          const eventKey = `on${upperFirst(field.changeEvent || 'change')}`;
          // 重写事件。 模板组件其他事件可以用 componentProps 植入 列如  onInput 、 onClick ..
          const on = {
            [eventKey]: function (...args: any[]) {
              //TODO prop 日期选择范围 `[startProp, endProp]`
              // const [e] = args;
              // const target = e ? e.target : null;
              // log(`change: ${componentName}`, target, e, args);
              // set(formData.value, field.prop, args[0]);
              // open.prototype.isChangeFormData = true;
              form?.change?.(
                {
                  params: args,
                  field: field,
                  formData: formData.value,
                  type: data.type,
                },
                ...args,
              );
            },
          };

          const componentName = form?.component as Component;
          const Comp = componentMap.get(componentName) as ReturnType<typeof defineComponent>;
          const placeholder =
            (form?.formItem?.placeholder ??
            [
              'ApiRadio',
              'ApiCheckbox',
              'ApiSelect',
              'ApiCascader',
              'el-rate',
              'el-switch',
              'el-checkbox',
              'el-radio',
              'el-select',
              'el-cascader',
              'el-date-picker',
            ].includes(componentName))
              ? `请选择${label}`
              : `请输入${label}`;

          const componentProps = Object.assign(
            {},
            // 默认值
            {
              placeholder,
              disabled: disabled.value,
            },
            // 覆盖默认值
            form?.componentProps ?? {},
          ) as any;

          if (
            ['el-checkbox', 'el-radio', 'el-select'].includes(componentName) &&
            componentProps.options === undefined
          ) {
            componentProps.options = form?.formItem?.dict ?? [];
          }

          if (
            ['ApiRadio', 'ApiCheckbox', 'ApiSelect', 'ApiCascader', 'ApiTreeSelect'].includes(
              componentName,
            )
          ) {
            componentProps.prop = prop;
            componentProps.formData = formData.value;
            componentProps.componentProps = componentProps;
          }

          if (['cat', 'share'].includes(data.type) || disabled.value) {
            componentProps.placeholder = ' ';
          }

          const compAttr: any = {
            ...componentProps,
            ...on,
            // TODO: 去除 v-model value只进行赋值回显，在重写change事件中同步响应式修改内容。
            //  目的prop解析赋值：日期选择范围 `[startProp, endProp]` `formData.a.c` `formData[1].c`
            // value: formData.value[prop as string],
          };

          // console.log(compAttr, field)
          if (Comp) {
            return (
              <Comp {...compAttr} v-model={formData.value[prop as string]}>
                {getContentSlots(field)}
              </Comp>
            );
          } else {
            // TODO customComponent 函数式: (opt: { field: FormFieldItem; formData: Partial<D>; type: menuType }) => VNode
            return <span style="color: red">[未定义模板组件]</span>;
          }
        };

        const getCol = (fields?: FormFieldItem<any>[]) => {
          return (
            <ElRow {...(props.rowProps ?? {})}>
              {(fields ?? formFields.value).map((field: FormFieldItem<any>, index: number) => {
                const { colSlot, visible, span, prop, colProps, label, form, editHide, children } =
                  field;
                // v-if 是否显示
                if (isFunction(visible)) {
                  const flag = visible({
                    field: field,
                    formData: formData.value,
                    type: data.type,
                  });
                  if (flag) return null;
                }
                // v-if 编辑时排除显示的字段
                if (editHide && data.type === 'edit') {
                  return null;
                }

                const slot = slots['col_' + (prop as string)] ?? colSlot;
                // ElCol插槽 处理
                if (isFunction(slot)) {
                  return slot({
                    field: field,
                    formData: formData.value,
                    type: data.type,
                  });
                }
                // warn(index + prop);
                return (
                  <ElCol span={span ?? 24} key={index + (prop as string)} {...(colProps ?? {})}>
                    {children?.length ? (
                      getCol(children)
                    ) : (
                      <ElFormItem
                        label={label}
                        prop={prop}
                        {...(form?.formItem ?? {})}
                        style="width: 100%"
                      >
                        {getContent.bind(this, field)}
                      </ElFormItem>
                    )}
                  </ElCol>
                );
              })}
            </ElRow>
          );
        };

        const dialogSlots = () => {
          return {
            header: isFunction(slots.header)
              ? slots.header.bind(this, {
                  formData: formData.value,
                  formRef: formRef.value,
                  type: data.type,
                  close: close,
                  loading: loading.value,
                })
              : null,
            footer: isFunction(slots.footer)
              ? slots.footer.bind(this, {
                  formData: formData.value,
                  formRef: formRef.value,
                  type: data.type,
                  close: close,
                  loading: loading.value,
                })
              : () => (
                  <div class="dialog-footer">
                    <el-button onClick={close.bind(this, true, true)}>
                      {props.cancelButtonText}
                    </el-button>
                    {['edit', 'add'].includes(data.type) ? (
                      <el-button
                        loadingIcon="Eleme"
                        loading={loading.value}
                        type="primary"
                        onClick={submit}
                      >
                        {props.confirmButtonText}
                      </el-button>
                    ) : null}
                  </div>
                ),
          };
        };

        const formContent = () => (
          <div style={{ padding: '0 10px', height: props.height }}>
            <ElForm
              class="base-model-form"
              v-loading={loading.value}
              element-loading-text="加载中..."
              element-loading-spinner={`
                    <path class="path" d="
                      M 30 15
                      L 28 17
                      M 25.61 25.61
                      A 15 15, 0, 0, 1, 15 30
                      A 15 15, 0, 1, 1, 27.99 7.5
                      L 15 15
                    " style="stroke-width: 4px; fill: rgba(0, 0, 0, 0)"/>
                  `}
              ref={formRef}
              model={formData.value}
              disabled={disabled.value}
              {...omit(props.formProps ?? {}, 'disabled')}
              rules={rules.value}
            >
              {getCol()}
            </ElForm>
          </div>
        );
        return (
          <div>
            {data.show && !!props.isPopup ? (
              <ElDialog
                title={title.value}
                v-model={data.show}
                {...(omit(props.dialogProps, 'width', 'title') ?? {})}
                width={props.width ?? props.dialogProps?.width}
                v-slots={dialogSlots()}
              >
                {formContent()}
              </ElDialog>
            ) : !props.isPopup && data.show ? (
              <>
                <div style="padding: 10px 0">{dialogSlots().header?.()}</div>
                {formContent()}
                <div style="padding: 10px 0">{dialogSlots().footer?.()}</div>
              </>
            ) : null}
          </div>
        );
      };
    },
  });
</script>

<style lang="scss" scoped>
  :deep(.el-select--default),
  :deep(.el-picker .el-input__wrapper),
  :deep(.el-select-v2--default),
  :deep(.el-date-editor.el-input),
  :deep(.el-date-editor.el-input__wrapper),
  :deep(.el-select-v2) {
    width: 100% !important;
  }

  .el-form--inline .el-form-item {
    margin-right: 0;
  }

  :deep(.base-model-form) {
    --el-disabled-border-color: rgb(255 255 255 / 0%);
    --el-disabled-bg-color: #f1f5fc;
    --el-disabled-text-color: #666;
    //width: calc(var(--el-dialog-width) - 70px) !important;
    //background: red;
    width: calc(100%) !important;

    * {
      box-sizing: border-box;
    }
  }
</style>
